/*
 * ChecksumFrame.java
 *
 * Copyright (C) 2009 Zhao Yi
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.zhyi.checksumz.ui;

import com.zhyi.checksumz.common.Checksumz;
import com.zhyi.checksumz.util.ChecksumzToolkit;
import com.zhyi.checksumz.util.Context;
import com.zhyi.zylib.toolkit.ExceptionToolkit;
import com.zhyi.zylib.toolkit.SwingToolkit;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.GroupLayout.ParallelGroup;
import javax.swing.GroupLayout.SequentialGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.WindowConstants;

/**
 * The main frame.
 */
public class ChecksumzFrame extends JFrame implements ActionListener, WindowListener {

    private final Font checksumFont = new Font("Courier New", Font.PLAIN, 12);
    private List<JCheckBox> checksumCheckBoxes;
    private List<JTextField> checksumTextFields;
    private List<Checksumz> checksums;
    private SwingWorker<List<String>, Void> task;
    private OptionsDialog optionsDialog;

    public ChecksumzFrame() {
        initComponents();
        initChecksumComponents();
        pack();
    }

    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        buttonGroup = new ButtonGroup();
        fileChooser = new JFileChooser();
        fileRadioButton = new JRadioButton();
        textRadioButton = new JRadioButton();
        fileButton = new JButton();
        fileTextField = new JTextField();

        SwingToolkit.addPopupMenuForTextComponent(fileTextField, true);
        label = new JLabel();
        charsetComboBox = new JComboBox(Charset.availableCharsets().values().toArray());
        charsetComboBox.setSelectedItem(Charset.defaultCharset());
        goButton = new JButton();
        scrollPane = new JScrollPane();
        textArea = new JTextArea();
        SwingToolkit.addPopupMenuForTextComponent(textArea, true);
        separator = new JSeparator();
        checksumPanel = new JPanel();
        toolBar = new JToolBar();
        cancelButton = new JButton();
        progressBar = new JProgressBar();
        aboutButton = new JButton();
        selectAllCheckBox = new JCheckBox();
        optionsButton = new JButton();

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setTitle("Checksumz");
        setResizable(false);
        addWindowListener(this);

        buttonGroup.add(fileRadioButton);
        fileRadioButton.setSelected(true);
        fileRadioButton.addActionListener(this);

        buttonGroup.add(textRadioButton);
        textRadioButton.addActionListener(this);

        fileButton.setText("File:");
        fileButton.addActionListener(this);

        label.setText("Text of Charset:");

        charsetComboBox.setEnabled(false);

        goButton.setText("Go");
        goButton.addActionListener(this);

        textArea.setColumns(20);
        textArea.setLineWrap(true);
        textArea.setRows(5);
        textArea.setWrapStyleWord(true);
        textArea.setEnabled(false);
        scrollPane.setViewportView(textArea);

        checksumPanel.setLayout(null);

        toolBar.setFloatable(false);
        toolBar.setRollover(true);

        cancelButton.setText("×");
        cancelButton.setToolTipText("Cancel");
        cancelButton.setEnabled(false);
        cancelButton.setFocusable(false);
        cancelButton.setHorizontalTextPosition(SwingConstants.CENTER);
        cancelButton.setVerticalTextPosition(SwingConstants.BOTTOM);
        cancelButton.addActionListener(this);
        toolBar.add(cancelButton);

        progressBar.setString("Ready");
        progressBar.setStringPainted(true);

        aboutButton.setText("About...");
        aboutButton.addActionListener(this);

        selectAllCheckBox.setText("Select/Unselect All");
        selectAllCheckBox.addActionListener(this);

        optionsButton.setText("Options...");
        optionsButton.addActionListener(this);

        GroupLayout layout = new GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addGroup(layout.createParallelGroup(Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGroup(layout.createParallelGroup(Alignment.TRAILING)
                                    .addGroup(Alignment.LEADING, layout.createSequentialGroup()
                                        .addComponent(textRadioButton)
                                        .addPreferredGap(ComponentPlacement.RELATED)
                                        .addComponent(label)
                                        .addPreferredGap(ComponentPlacement.RELATED)
                                        .addComponent(charsetComboBox, 0, 208, Short.MAX_VALUE))
                                    .addGroup(Alignment.LEADING, layout.createSequentialGroup()
                                        .addComponent(fileRadioButton)
                                        .addPreferredGap(ComponentPlacement.RELATED)
                                        .addComponent(fileButton)
                                        .addPreferredGap(ComponentPlacement.RELATED)
                                        .addComponent(fileTextField, GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE)))
                                .addPreferredGap(ComponentPlacement.RELATED)
                                .addComponent(goButton))
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(selectAllCheckBox)
                                .addPreferredGap(ComponentPlacement.RELATED)
                                .addComponent(separator, GroupLayout.DEFAULT_SIZE, 239, Short.MAX_VALUE))
                            .addComponent(checksumPanel, GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(31, 31, 31)
                        .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 359, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(optionsButton)
                        .addPreferredGap(ComponentPlacement.RELATED)
                        .addComponent(aboutButton)
                        .addPreferredGap(ComponentPlacement.RELATED, 15, Short.MAX_VALUE)
                        .addComponent(progressBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(ComponentPlacement.RELATED)
                        .addComponent(toolBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );

        layout.linkSize(SwingConstants.HORIZONTAL, new Component[] {aboutButton, optionsButton});

        layout.setVerticalGroup(
            layout.createParallelGroup(Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(Alignment.TRAILING)
                            .addComponent(fileRadioButton)
                            .addGroup(layout.createParallelGroup(Alignment.BASELINE)
                                .addComponent(fileButton)
                                .addComponent(fileTextField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
                        .addPreferredGap(ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(Alignment.TRAILING)
                            .addComponent(textRadioButton)
                            .addGroup(layout.createParallelGroup(Alignment.BASELINE)
                                .addComponent(label)
                                .addComponent(charsetComboBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))))
                    .addComponent(goButton, GroupLayout.PREFERRED_SIZE, 49, GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(scrollPane, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addGroup(layout.createParallelGroup(Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(8, 8, 8)
                        .addComponent(selectAllCheckBox))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(18, 18, 18)
                        .addComponent(separator, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(checksumPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(Alignment.LEADING, false)
                    .addGroup(layout.createParallelGroup(Alignment.BASELINE)
                        .addComponent(aboutButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(optionsButton))
                    .addComponent(toolBar, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(progressBar, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }

    // Code for dispatching events from components to event handlers.

    public void actionPerformed(java.awt.event.ActionEvent evt) {
        if (evt.getSource() == fileRadioButton) {
            ChecksumzFrame.this.fileRadioButtonActionPerformed(evt);
        }
        else if (evt.getSource() == textRadioButton) {
            ChecksumzFrame.this.textRadioButtonActionPerformed(evt);
        }
        else if (evt.getSource() == fileButton) {
            ChecksumzFrame.this.fileButtonActionPerformed(evt);
        }
        else if (evt.getSource() == goButton) {
            ChecksumzFrame.this.goButtonActionPerformed(evt);
        }
        else if (evt.getSource() == cancelButton) {
            ChecksumzFrame.this.cancelButtonActionPerformed(evt);
        }
        else if (evt.getSource() == aboutButton) {
            ChecksumzFrame.this.aboutButtonActionPerformed(evt);
        }
        else if (evt.getSource() == selectAllCheckBox) {
            ChecksumzFrame.this.selectAllCheckBoxActionPerformed(evt);
        }
        else if (evt.getSource() == optionsButton) {
            ChecksumzFrame.this.optionsButtonActionPerformed(evt);
        }
    }

    public void windowActivated(java.awt.event.WindowEvent evt) {
    }

    public void windowClosed(java.awt.event.WindowEvent evt) {
    }

    public void windowClosing(java.awt.event.WindowEvent evt) {
        if (evt.getSource() == ChecksumzFrame.this) {
            ChecksumzFrame.this.formWindowClosing(evt);
        }
    }

    public void windowDeactivated(java.awt.event.WindowEvent evt) {
    }

    public void windowDeiconified(java.awt.event.WindowEvent evt) {
    }

    public void windowIconified(java.awt.event.WindowEvent evt) {
    }

    public void windowOpened(java.awt.event.WindowEvent evt) {
    }// </editor-fold>//GEN-END:initComponents

    private void aboutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_aboutButtonActionPerformed
        JOptionPane.showMessageDialog(this,
                "<html><h2>Checksumz</h2>"
                + "Used to compute checksums for file or text.<br><br>"
                + "Copyright &copy; 2009 Zhao Yi (shinzey@msn.com)<br>"
                + "Licensed under GNU General Public License Version 3</html>",
                "About Checksumz", JOptionPane.INFORMATION_MESSAGE);
    }//GEN-LAST:event_aboutButtonActionPerformed

    private void fileButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fileButtonActionPerformed
        if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            fileTextField.setText(fileChooser.getSelectedFile().getPath());
        }
    }//GEN-LAST:event_fileButtonActionPerformed

    private void goButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_goButtonActionPerformed
        final long begin = System.currentTimeMillis();
        for (JTextField textField : checksumTextFields) {
            textField.setText("");
        }
        enableOperationComponents(false);
        progressBar.setString("Computng...");
        task = new SwingWorker<List<String>, Void>() {

            private List<Integer> selectedIndexes;
            private List<Checksumz> selectedChecksums;

            @Override
            protected List<String> doInBackground() throws Exception {
                selectedIndexes = new ArrayList<Integer>();
                selectedChecksums = new ArrayList<Checksumz>();
                int index = 0;
                for (JCheckBox checkBox : checksumCheckBoxes) {
                    if (checkBox.isSelected()) {
                        selectedIndexes.add(index);
                        selectedChecksums.add(checksums.get(index));
                    }
                    index++;
                }

                if (fileRadioButton.isSelected()) {
                    return ChecksumzToolkit.computeChecksumsForFile(
                            new File(fileTextField.getText()), selectedChecksums);
                }
                return ChecksumzToolkit.computeChecksumsForText(
                        textArea.getText(), selectedChecksums,
                        (Charset) charsetComboBox.getSelectedItem());
            }

            @Override
            protected void done() {
                try {
                    if (!isCancelled()) {
                        List<String> results = get();
                        for (int i = 0; i < results.size(); i++) {
                            checksumTextFields.get(selectedIndexes.get(i))
                                    .setText(results.get(i));
                        }
                    }
                } catch (Exception ex) {
                    JOptionPane.showMessageDialog(ChecksumzFrame.this,
                            ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
                } finally {
                    enableOperationComponents(true);
                    progressBar.setString("Finished in "
                            + (System.currentTimeMillis() - begin) + "ms");
                }
            }

        };
        task.execute();
    }//GEN-LAST:event_goButtonActionPerformed

    private void cancelButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
        int option = JOptionPane.showConfirmDialog(this,
                "Do you really want to cancel the checksum computing?", "Cancel",
                JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
        if (option == JOptionPane.YES_OPTION) {
            task.cancel(true);
        }
    }//GEN-LAST:event_cancelButtonActionPerformed

    private void fileRadioButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fileRadioButtonActionPerformed
        enableInputComponents(fileRadioButton.isSelected());
    }//GEN-LAST:event_fileRadioButtonActionPerformed

    private void textRadioButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_textRadioButtonActionPerformed
        enableInputComponents(!textRadioButton.isSelected());
    }//GEN-LAST:event_textRadioButtonActionPerformed

    private void selectAllCheckBoxActionPerformed(ActionEvent evt) {//GEN-FIRST:event_selectAllCheckBoxActionPerformed
        boolean selected = selectAllCheckBox.isSelected();
        for (JCheckBox checkBox : checksumCheckBoxes) {
            checkBox.setSelected(selected);
        }
    }//GEN-LAST:event_selectAllCheckBoxActionPerformed

    private void optionsButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_optionsButtonActionPerformed
        if (optionsDialog == null) {
            optionsDialog = new OptionsDialog(this, true);
        }
        optionsDialog.reset();
        optionsDialog.setLocationRelativeTo(this);
        optionsDialog.setVisible(true);
    }//GEN-LAST:event_optionsButtonActionPerformed

    private void formWindowClosing(WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        try {
            List<String> chosenChecksums = new ArrayList<String>();
            for (JCheckBox checkBox : checksumCheckBoxes) {
                if (checkBox.isSelected()) {
                    chosenChecksums.add(checkBox.getName());
                }
            }
            Context.setChosenChecksums(chosenChecksums);
            Context.save();
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(
                    this, ExceptionToolkit.getRootCause(ex).getMessage(),
                    "Error", JOptionPane.ERROR_MESSAGE);
        }
    }//GEN-LAST:event_formWindowClosing

    private void initChecksumComponents() {
        checksumCheckBoxes = new ArrayList<JCheckBox>();
        checksumTextFields = new ArrayList<JTextField>();
        checksums = new ArrayList<Checksumz>();
        Map<String, Checksumz> checksumMap = ChecksumzToolkit.getSupportedChecksums();
        List<String> chosenChecksums = Context.getChosenChecksums();
        for (String name : checksumMap.keySet()) {
            JCheckBox checkBox = new JCheckBox(
                    name + ":", chosenChecksums.contains(name));
            checkBox.setName(name);
            checksumCheckBoxes.add(checkBox);
            checksumTextFields.add(generateChecksumTextField());
            checksums.add(checksumMap.get(name));
        }

        GroupLayout layout = new GroupLayout(checksumPanel);
        checksumPanel.setLayout(layout);
        layout.setAutoCreateGaps(true);

        ParallelGroup leftGroup = layout.createParallelGroup();
        for (JCheckBox checkBox : checksumCheckBoxes) {
            leftGroup.addComponent(checkBox);
        }
        ParallelGroup rightGroup = layout.createParallelGroup();
        for (JTextField textField : checksumTextFields) {
            rightGroup.addComponent(textField);
        }
        layout.setHorizontalGroup(layout.createSequentialGroup()
                .addGroup(leftGroup).addGroup(rightGroup));

        int amount = checksumCheckBoxes.size();
        SequentialGroup verticalGroup = layout.createSequentialGroup();
        for (int i = 0; i < amount; i++) {
            verticalGroup.addGroup(layout.createParallelGroup(Alignment.BASELINE)
                    .addComponent(checksumCheckBoxes.get(i))
                    .addComponent(checksumTextFields.get(i)));
        }
        layout.setVerticalGroup(verticalGroup);
    }

    private JTextField generateChecksumTextField() {
        final JTextField checksumTextField = new JTextField();
        checksumTextField.setEditable(false);
        checksumTextField.setFont(checksumFont);

        JPopupMenu popupMenu = new JPopupMenu();
        JMenuItem copyMenuItem = new JMenuItem("Copy");
        copyMenuItem.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
        copyMenuItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                checksumTextField.selectAll();
                checksumTextField.copy();
            }

        });
        popupMenu.add(copyMenuItem);
        checksumTextField.setComponentPopupMenu(popupMenu);

        return checksumTextField;
    }

    private void enableInputComponents(boolean enabled) {
        fileButton.setEnabled(enabled);
        fileTextField.setEnabled(enabled);
        charsetComboBox.setEnabled(!enabled);
        textArea.setEnabled(!enabled);
    }

    private void enableOperationComponents(boolean enabled) {
        goButton.setEnabled(enabled);
        cancelButton.setEnabled(!enabled);
        progressBar.setIndeterminate(!enabled);
    }

    public static void main(String args[]) throws Exception {
        Context.load();
        SwingToolkit.initSystemlookAndFeel();
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new ChecksumzFrame();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private JButton aboutButton;
    private ButtonGroup buttonGroup;
    private JButton cancelButton;
    private JComboBox charsetComboBox;
    private JPanel checksumPanel;
    private JButton fileButton;
    private JFileChooser fileChooser;
    private JRadioButton fileRadioButton;
    private JTextField fileTextField;
    private JButton goButton;
    private JLabel label;
    private JButton optionsButton;
    private JProgressBar progressBar;
    private JScrollPane scrollPane;
    private JCheckBox selectAllCheckBox;
    private JSeparator separator;
    private JTextArea textArea;
    private JRadioButton textRadioButton;
    private JToolBar toolBar;
    // End of variables declaration//GEN-END:variables

}
